#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

#include <boost/format.hpp>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_container.hpp>
#include <boost/spirit/include/phoenix_statement.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/support_argument.hpp>

#include "p-machine.hh"

using namespace boost::spirit;
using namespace boost::spirit::qi;
using namespace boost::spirit::ascii;

using boost::phoenix::ref;
using boost::phoenix::push_back;
using boost::phoenix::val;
using boost::phoenix::construct;


template <typename Iterator>
struct calculator : grammar<Iterator, boost::spirit::ascii::space_type> {
  calculator(pcode_array& code) : calculator::base_type(expression), code(code) {
    expression =
      (lit("if") > bool_exprs > lit("then")) [push_back(boost::phoenix::ref(code), pcode_end_on_false)] >> *(action)
      | +(action)
      ;
    expression.name("expression");
    
    bool_exprs = bool_expr_not
      >> *( lit("and") > bool_expr_not [push_back(boost::phoenix::ref(code), pcode_and)])
      ;
    bool_exprs.name("bool expressions");
    
    bool_expr_not = bool_expr
      | lit("not") > bool_expr [push_back(boost::phoenix::ref(code), pcode_lnot)]
      ;
    bool_expr_not.name("bool_expr_not");

    bool_expr = cond_eq | cond_lt | cond_gt | cond_visited | cond_carried | cond_avail;
    bool_expr.name("boolean expression");
    
    cond_eq = value > '=' > value [push_back(boost::phoenix::ref(code), pcode_cmp_eq)];
    cond_eq.name("cond_eq");
    cond_lt = value > '<' > value [push_back(boost::phoenix::ref(code), pcode_cmp_lt)];
    cond_lt.name("cond_lt");
    cond_gt = value > '>' > value [push_back(boost::phoenix::ref(code), pcode_cmp_gt)];
    cond_gt.name("cond_gt");

    cond_visited = 
      lit("visited") [push_back(boost::phoenix::ref(code), pcode_load_here)
		      , push_back(boost::phoenix::ref(code), pcode_load_visited)
		      ]
      ;
    cond_visited.name("cond_visited");

    cond_carried = 
      lit("carried") > objnum [push_back(boost::phoenix::ref(code), pcode_load_obj_pos)
			       , push_back(boost::phoenix::ref(code), pcode_load_immediate)
			       , push_back(boost::phoenix::ref(code), (short)POS_CARRIED)
			       , push_back(boost::phoenix::ref(code), pcode_cmp_eq)
			       ]
      ;
    cond_avail.name("cond_carried");

    cond_avail = 
      lit("available") > objnum [push_back(boost::phoenix::ref(code), pcode_load_obj_pos)
				 , push_back(boost::phoenix::ref(code), pcode_dup)
				 , push_back(boost::phoenix::ref(code), pcode_load_immediate)
				 , push_back(boost::phoenix::ref(code), (short)POS_CARRIED)
				 , push_back(boost::phoenix::ref(code), pcode_cmp_eq)
				 , push_back(boost::phoenix::ref(code), pcode_exchange)
				 , push_back(boost::phoenix::ref(code), pcode_load_here)
				 , push_back(boost::phoenix::ref(code), pcode_cmp_eq)
				 , push_back(boost::phoenix::ref(code), pcode_and)
				 ]
      ;
    cond_avail.name("cond_avail");

    objnum = number
      | lit("obj1") [push_back(boost::phoenix::ref(code), pcode_load_obj1)]
      ;
    objnum.name("objnum");

    value = objnum
      | lit("here") [push_back(boost::phoenix::ref(code), pcode_load_here)]
      | (lit("posof") > number) [push_back(boost::phoenix::ref(code), pcode_load_obj_pos)]
      ;
    value.name("value");

    number = uint_ [
		    push_back(boost::phoenix::ref(code), pcode_load_immediate),
		    push_back(boost::phoenix::ref(code), _1)
		    ]
      ;
    number.name("number");

    action = 
      ( lit("long") [push_back(boost::phoenix::ref(code), pcode_look_long)] )
      | ( lit("short") [push_back(boost::phoenix::ref(code), pcode_look_short)] )
      | ( lit("msg") > number ) [push_back(boost::phoenix::ref(code), pcode_write_mesg)]
      | lit("exits") [push_back(boost::phoenix::ref(code), pcode_print_exits)]
      | lit("objects") [push_back(boost::phoenix::ref(code), pcode_print_objs)]
      | lit("nl") [push_back(boost::phoenix::ref(code), pcode_nl)]
      | lit("inv") [push_back(boost::phoenix::ref(code), pcode_inventory)]
      | lit("true") [push_back(boost::phoenix::ref(code), pcode_load_true)]
      | lit("false") [push_back(boost::phoenix::ref(code), pcode_load_false)]
      ;
    action.name("action");
    
    on_error<fail>
      (
       expression
       , std::cout
       << val("Error! Expecting ")
       << _4                               // what failed?
       << val(" here: \"")
       << construct<std::string>(_3, _2)   // iterators to error-pos, end
       << val("\"")
       << std::endl
       );
  }
  
  rule<Iterator, boost::spirit::ascii::space_type> expression, bool_exprs, bool_expr, number, action, objnum, value, cond_avail, cond_carried, cond_visited, cond_eq, cond_lt, cond_gt, bool_expr_not;
  pcode_array &code;
};

template <typename Grammar>
bool compile(Grammar const& calc, std::string const& expr) {
  std::string::const_iterator iter = expr.begin();
  std::string::const_iterator end = expr.end();
  bool r = phrase_parse(iter, end, calc, boost::spirit::ascii::space);
  
  if (r && iter == end) {
    std::cout << "-------------------------\n";
    std::cout << "Parsing succeeded\n";
    std::cout << "-------------------------\n";
    return true;
  } else {
    std::cout << "-------------------------\n";
    std::cout << "Parsing failed\n";
    std::cout << "Not parsed: '" << std::string(iter, end) << "'.\n";
    std::cout << "-------------------------\n";
    return false;
  }
}

int main() {
  std::cout << "Expression compiler...\n\n";
  std::cout << "Type an expression...or [q or Q] to quit\n\n";

  typedef std::string::const_iterator iterator_type;
  typedef calculator<iterator_type> calculator;

  pcode_array code;		//  Our VM code
  calculator calc(code);	//  Our grammar

  std::string str;
  while (std::getline(std::cin, str))
    {
      if (str.empty() || str[0] == 'q' || str[0] == 'Q')
	break;

      code.clear();
      if (::compile(calc, str)) {
	std::cout << "OK: ";
      } else {
	std::cout << "??: ";
      }
      for(auto it = code.begin(); it != code.end(); ++it) {
	std::cout << boost::format(" %04hX") % *it;
      }
      std::cout << std::endl;
    }
  return 0;
}


